#include <stdio.h>
#include <stdlib.h>
#include <ucontext.h>
#include <signal.h>
#include <sys/time.h>
#include "queue.h"
#include "task.h"

void print_priority( );

#define stack_size 32768

static int next_task_id;							//id da proxima tarefa
static task_t * running_task_ptr;					//ponteiro para tarefa em execucao
static task_t * ready_tasks;						//lista de tarefas prontas
static task_t main_task;							//tarefa principal
static task_t dispatcher;							//tarefa do dispatcher
static int user_tasks; 								//quantidade de tarefas de usuário
static task_t * current_user_task = NULL;			//ponteiro para a tarefa de usuário corrente
struct itimerval timer;								//estrutura para tempo do temporizador
struct sigaction action;							//estrutura para recebimento de sinais do sistema
void timer_handler ();								//declaracao da funcao timer_handler
static int ticks;									//quantidade de ticks de relogio, acessado por systime
void dispatcher_body (void * arg)
{
	task_t * next;
	while (user_tasks > 0)
	{
		next = scheduler(); //
		if (next)
		{
			current_user_task = next;
			task_switch(next);
			//comandos depois de task_switch()
		}
	}
	task_exit(0);
}

void task_init(){
	//inicializando variaveis com valores iniciais
	next_task_id = 1;
	running_task_ptr = &main_task;
	ready_tasks = NULL;
	user_tasks = -1;
	
	//configurando o contexto do dispatcher
	getcontext(&(main_task.context));					//salva o contexto da tarefa main
	main_task.tid = 0;									//id da tarefa main e 0
	main_task.context.uc_link = 0;						//finalizar programa ao terminar tarefa
	setvbuf (stdout, 0, _IONBF, 0);						//desativa o buffer da saida padrao (stdout), usado pela funcao printf
	
	//criação do dispatcher como tarefa de sistema fora da fila de tarefas prontas
	task_create(&dispatcher, dispatcher_body, NULL);	//criacao da tarefa dispatcher
	queue_remove((queue_t **) &ready_tasks, (queue_t *) &dispatcher);
														//tirar o dispatcher da fila
	//condiguracao do temporizador
	//tempo até o primeiro disparo
	timer.it_value.tv_usec = 1000 ;
	timer.it_value.tv_sec  = 0 ;
	//tempo entre cada disparo posterior ao primeiro (1 ms)
	timer.it_interval.tv_usec = 1000 ;
	timer.it_interval.tv_sec  = 0 ;
	//iniciar o timer, sucesso nesta etapa marca tim_success como 0, erro -1 
	int tim_success = setitimer (ITIMER_REAL, &timer, 0);

	//configurar o tratador do timer
	action.sa_handler = timer_handler;
	sigemptyset (&action.sa_mask);
	action.sa_flags = 0;
	//iniciar o tratador, sucesso nesta etapa marca sig_success como 0, erro -1
	int sig_success = sigaction (SIGALRM, &action, 0);
	
	#ifdef DEBUG	//se o debug estiver habilitado
	//em caso de erro na criacao do tratador do timer
	if (sig_success < 0){
		printf("erro ao criar tratador do timer");
		exit (1);
	} else {
		printf("tratador do timer criado com sucesso");
	}
	
	//em caso de erro na criacao do timer
	if (sig_success < 0){
		printf("erro ao criar timer");
		exit (1);
	} else {
		printf("timer criado com sucesso");
	}
	printf("task_init inicializou a biblioteca");		//mostrar na tela que a biblioteca de tarefas foi inicializada
	#endif
	}

int task_create (task_t * task_ptr, void (*start_routine)(void *),  void * arg){
	//criacao de contexto e de pilha
	getcontext(&(task_ptr->context));					//salva o contexto atual da tarefa
	char * stack;										//ponteiro para a pilha
	stack = malloc (stack_size);						//aloca espaco de memoria para a pilha
	if (stack){											//se for possivel criar a pilha
		task_ptr->context.uc_stack.ss_sp = stack;		//salva o parametro stack pointer das variaveis de contexto
		task_ptr->stack_start = stack;					//salva ponteiro para inicio da pilha
		task_ptr->context.uc_stack.ss_size = stack_size;//salva o parametro de tamanho de pilha para este contexto, definido por stack_size
		task_ptr->context.uc_stack.ss_flags = 0;		//define as flags do contexto
		task_ptr->context.uc_link = 0;					//uc_link = 0 finaliza tarefa
	} 
	else{												//se nao for possivel criar a pilha
		perror("error creating stack: ");				//avisa ao usuario erro
		#ifdef DEBUG									//se o debug estiver habilitado
		printf("task_create falhou em executar");		//mostrar na tela que a funcao foi executada e criou a tarefa
		#endif
		return (-1);									//valor de retorno em caso de erro e -1
	}
	makecontext(&(task_ptr->context), (void (*) (void)) start_routine, 1, arg);
														//salva alterações no contexto da tarefa a ser criada
	
	task_ptr->tid = next_task_id;						//dar id para tarefa
	next_task_id++;										//ir para a proxima id livre
	
	//limpar valores dos ponteiros next e prev da tarefa a ser adicionada
	task_ptr->next = NULL;
	task_ptr->prev = NULL;
	task_ptr->sta_priority = 0;							//a prioridade da tarefa e 0
	task_ptr->dyn_priority = 0;
	queue_append ((queue_t **) &ready_tasks, (queue_t *) task_ptr);
														//adicionar tarefa para fila de tarefas
	user_tasks++;										//contabilizar mais uma tarefa pronta
	task_ptr->quantum = 20;								//valor inicial de quantum para tarefas de 20 ticks de relógio
	
	task_ptr->exec_time = ticks;						//registrar na tarefa a quantidade de ticks dados ate a criacao
	
	#ifdef DEBUG										//se o debug estiver habilitado
	printf("task_create criou a tarefa %d", task_ptr->tid);	
														//mostrar na tela que a funcao foi executada e criou a tarefa
	#endif
	return 0;
}

int task_switch (task_t *task_ptr){
	/*alterna para o processamento de outra tarefa 
	task_ptr aponta para a tarefa a ser processada */
	running_task_ptr->proc_time += systime() - running_task_ptr->last_start;
													//armazenar o tempo de processador da tarefa em execucao antes de fazer a troca	
	task_t * old_task_ptr = running_task_ptr;		//a tarefa agora corrente sera suspensa
	running_task_ptr = task_ptr;					//a nova tarefa corrente e a tarefa apontada por task_ptr
	running_task_ptr->last_start = systime();		//a nova tarefa corrente comecara a executar em seguida do swapcontext
	running_task_ptr->activations++;				//incrementar a quantidade de ativacoes da tarefa
	
	#ifdef DEBUG									//se o debug estiver habilitado
	printf("task_switch encerrou tarefa %d e iniciou tarefa %d",old_task_ptr->tid, task_ptr->tid);
													//mostrar na tela que a funcao foi executada e trocou de tarefa em execucao
	#endif

	return swapcontext (&(old_task_ptr->context), &(task_ptr->context));
													//retorna 0 se foi possivel fazer troca de contexto
}

void task_exit (int exit_code){
	//finaliza uma tarefa
	//retorna para a tarefa principal
	int id_finalizado = running_task_ptr->tid;
	if (current_user_task == running_task_ptr) {
		current_user_task = NULL;
	}
	queue_remove((queue_t **) &ready_tasks,(queue_t *)running_task_ptr);
	user_tasks--;
	printf("Task %d, execution time %d ms, processor time %d ms, %d activations", running_task_ptr->tid,
		systime() - running_task_ptr->exec_time, running_task_ptr->proc_time, running_task_ptr->activations);
														//imprimir mensagem de finalizacao de tarefa
	if (id_finalizado == 1)							//se for o dispatcher que está chamando esta funcao
	{
		task_switch(&main_task);					//retornar para a tarefa principal
	} else {										//senao, retornar ao dispatcher
		task_switch(&dispatcher);
	}

	#ifdef DEBUG									//se o debug estiver habilitado
	printf("task_exit encerrou tarefa %d \n", id_finalizado);				
													//mostrar na tela que a funcao foi executada e finalizou a tarefa
	#endif
}

int task_id(){
	#ifdef DEBUG									//se o debug estiver habilitado
	printf("task_id retornou %d como tarefa ativa", current_user_task->tid);
													//mostrar na tela qual é a tarefa ativa
	#endif
	return current_user_task->tid;
}

void task_yield ()
{		
		task_switch(&dispatcher);					//retorna para o dispatcher
}

task_t * scheduler(){
	task_t * cursor_t = ready_tasks;				//cursor para analise de tarefa
	task_t * high_prio_task = current_user_task;	//tarefa com maior prioridade, se nenhuma tarefa tiver prioridade maior que o primeiro elemento da
													//fila de tarefas prontas, retornará o primeiro elemento da fila de tarefas prontas
	int high_prio = 20;								//inicia com menor prioridade
	
	if (current_user_task != NULL){					//se tiver alguma tarefa em execucao
		high_prio = current_user_task->dyn_priority;//a maior prioridade e dessa tarefa
	}

	do{												//loop para procurar a proxima tarefa a ser executada
		if (high_prio > cursor_t->dyn_priority){	//se o cursor esta num elemento de prioridade maior que a maior prioridade ate agora (em um valor menor)
			high_prio_task = cursor_t;				//a tarefa de maior prioridade e essa
			high_prio = cursor_t->dyn_priority;		//atualiza maior prioridade
		}
		cursor_t = cursor_t->next;					//va para a proxima tarefa
	}while (cursor_t != ready_tasks);
	
	//envelhecendo as tarefas
	cursor_t = ready_tasks;
	do{
		if (cursor_t != current_user_task && cursor_t->dyn_priority > -20){	//se não for a tarefa corrente e a prioridade não for o limite minimo
			cursor_t->dyn_priority--;										//aumente a prioridade
		}
		cursor_t = cursor_t->next;											//va para a proxima tarefa
	}while (cursor_t != ready_tasks);
	high_prio_task->dyn_priority = high_prio_task->sta_priority;            // tarefa selecionada recebe prioridade estática
	//print_priority( );
	//printf("sch returns %d \n",high_prio_task->tid);
	return high_prio_task;
}

int task_nice (int nice_level){
//tarefa utilizada para alterar a prioridade da tarefa corrente
int old_priority = running_task_ptr->sta_priority;							//armazena prioridade atual da tarefa em execucao
if (nice_level > -20 && nice_level < 20){									//se a nova prioridade estiver dentro dos limites
	running_task_ptr->sta_priority=nice_level;								//altera prioridade da tarefa em execucao para nice_level
	running_task_ptr->dyn_priority=nice_level;
}
if (nice_level < -20){														//se setiver abaixo do limite minimo, a prioridade e -20
	running_task_ptr->sta_priority = -20;
	running_task_ptr->dyn_priority = -20;
}
if (nice_level > 20){														//se estiver acima do limite maximo, a prioridade e 20
	running_task_ptr->sta_priority = 20;
	running_task_ptr->dyn_priority = 20;
}
return old_priority;														//ao terminar, retornar a prioridade antiga da tarefa corrente
}

void print_priority(){														//funcao utilizada para acompanhamento de execucao de tarefas
	task_t * cursor_t  = ready_tasks;
	printf("UTQ:");
	do {
		printf("id:%d p:%d;", cursor_t->tid, cursor_t->dyn_priority);		//imprime a identificacao da tarefa e a prioridade dinamica
		cursor_t = cursor_t->next;
	}while (cursor_t != ready_tasks);
	printf("\n");
}
void timer_handler (){
	if (running_task_ptr->tid != 0 && running_task_ptr != 1){				//se nao for main (tid = 0) ou dispatcher (tid = 1)
		if (running_task_ptr->quantum > 0){									//se o quantum da tarefa de usuario for maior que 0
			running_task_ptr->quantum--;									//decrementar quantum da tarefa
			return;															//finalizar rotina
		}
		if (running_task_ptr->quantum == 0){								//se o quantum da tarefa de usuario for 0 (fim de tempo)
			running_task_ptr->quantum = 20;									//retornar o quantum da tarefa ao valor inicial de quantum (20)
			task_yield();													//retornar ao dispatcher
		}
	}
	ticks++;																//incrementar a quantidade de ticks de relogio
}

int systime() {																//funcao para ler a quantidade de ticks de relogio
	return ticks;
}
